Full source code website bán hàng thương mại điện tử gần giống shopee
473.307 lượt xem;
1 <?php
2 /*
3 * smtp.php
4 *
5 * @(#) $Header: /home/mlemos/cvsroot/smtp/smtp.php,v 1.37 2004/10/05 04:00:46 mlemos Exp $
6 *
7 */
8
9 class smtp_class
10 {
11 var $user="";
12 var $realm="";
13 var $password="";
14 var $workstation="";
15 var $authentication_mechanism="";
16 var $host_name="";
17 var $host_port=25;
18 var $localhost="";
19 var $timeout=0;
20 var $data_timeout=0;
21 var $direct_delivery=0;
22 var $error="";
23 var $debug=0;
24 var $html_debug=0;
25 var $esmtp=1;
26 var $esmtp_host="";
27 var $esmtp_extensions=array();
28 var $maximum_piped_recipients=100;
29 var $exclude_address="";
30 var $getmxrr="GetMXRR";
31 var $pop3_auth_host="";
32 var $pop3_auth_port=110;
33
34 /* private variables - DO NOT ACCESS */
35
36 var $state="Disconnected";
37 var $connection=0;
38 var $pending_recipients=0;
39 var $next_token="";
40 var $direct_sender="";
41 var $connected_domain="";
42 var $result_code;
43
44 /* Private methods - DO NOT CALL */
45
46 Function Tokenize($string,$separator="")
47 {
48 if(!strcmp($separator,""))
49 {
50 $separator=$string;
51 $string=$this->next_token;
52 }
53 for($character=0;$character<strlen($separator);$character++)
54 {
55 if(GetType($position=strpos($string,$separator[$character]))=="integer")
56 $found=(IsSet($found) ? min($found,$position) : $position);
57 }
58 if(IsSet($found))
59 {
60 $this->next_token=substr($string,$found+1);
61 return(substr($string,0,$found));
62 }
63 else
64 {
65 $this->next_token="";
66 return($string);
67 }
68 }
69
70 Function OutputDebug($message)
71 {
72 $message.="\n";
73 if($this->html_debug)
74 $message=str_replace("\n","<br />\n",HtmlEntities($message));
75 echo $message;
76 flush();
77 }
78
79 Function SetDataAccessError($error)
80 {
81 $this->error=$error;
82 if(function_exists("socket_get_status"))
83 {
84 $status=socket_get_status($this->connection);
85 if($status["timed_out"])
86 $this->error.=": data access time out";
87 elseif($status["eof"])
88 $this->error.=": the server disconnected";
89 }
90 }
91
92 Function GetLine()
93 {
94 for($line="";;)
95 {
96 if(feof($this->connection))
97 {
98 $this->error="reached the end of data while reading from the SMTP server conection";
99 return("");
100 }
101 if(GetType($data=fgets($this->connection,100))!="string"
102 || strlen($data)==0)
103 {
104 $this->SetDataAccessError("it was not possible to read line from the SMTP server");
105 return("");
106 }
107 $line.=$data;
108 $length=strlen($line);
109 if($length>=2
110 && substr($line,$length-2,2)=="\r\n")
111 {
112 $line=substr($line,0,$length-2);
113 if($this->debug)
114 $this->OutputDebug("S $line");
115 return($line);
116 }
117 }
118 }
119
120 Function PutLine($line)
121 {
122 if($this->debug)
123 $this->OutputDebug("C $line");
124 if(!fputs($this->connection,"$line\r\n"))
125 {
126 $this->SetDataAccessError("it was not possible to send a line to the SMTP server");
127 return(0);
128 }
129 return(1);
130 }
131
132 Function PutData(&$data)
133 {
134 if(strlen($data))
135 {
136 if($this->debug)
137 $this->OutputDebug("C $data");
138 if(!fputs($this->connection,$data))
139 {
140 $this->SetDataAccessError("it was not possible to send data to the SMTP server");
141 return(0);
142 }
143 }
144 return(1);
145 }
146
147 Function VerifyResultLines($code,&$responses)
148 {
149 $responses=array();
150 Unset($this->result_code);
151 while(strlen($line=$this->GetLine($this->connection)))
152 {
153 if(IsSet($this->result_code))
154 {
155 if(strcmp($this->Tokenize($line," -"),$this->result_code))
156 {
157 $this->error=$line;
158 return(0);
159 }
160 }
161 else
162 {
163 $this->result_code=$this->Tokenize($line," -");
164 if(GetType($code)=="array")
165 {
166 for($codes=0;$codes<count($code) && strcmp($this->result_code,$code[$codes]);$codes++);
167 if($codes>=count($code))
168 {
169 $this->error=$line;
170 return(0);
171 }
172 }
173 else
174 {
175 if(strcmp($this->result_code,$code))
176 {
177 $this->error=$line;
178 return(0);
179 }
180 }
181 }
182 $responses[]=$this->Tokenize("");
183 if(!strcmp($this->result_code,$this->Tokenize($line," ")))
184 return(1);
185 }
186 return(-1);
187 }
188
189 Function FlushRecipients()
190 {
191 if($this->pending_sender)
192 {
193 if($this->VerifyResultLines("250",$responses)<=0)
194 return(0);
195 $this->pending_sender=0;
196 }
197 for(;$this->pending_recipients;$this->pending_recipients--)
198 {
199 if($this->VerifyResultLines(array("250","251"),$responses)<=0)
200 return(0);
201 }
202 return(1);
203 }
204
205 Function ConnectToHost($domain, $port, $resolve_message)
206 {
207 if(ereg('^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$',$domain))
208 $ip=$domain;
209 else
210 {
211 if($this->debug)
212 $this->OutputDebug($resolve_message);
213 if(!strcmp($ip=@gethostbyname($domain),$domain))
214 return("could not resolve host \"".$domain."\"");
215 }
216 if(strlen($this->exclude_address)
217 && !strcmp(@gethostbyname($this->exclude_address),$ip))
218 return("domain \"".$domain."\" resolved to an address excluded to be valid");
219 if($this->debug)
220 $this->OutputDebug("Connecting to host address \"".$ip."\"...");
221 if(($this->connection=($this->timeout ? @fsockopen($ip,$port,$errno,$error,$this->timeout) : @fsockopen($ip,$port))))
222 return("");
223 $error=($this->timeout ? strval($error) : "??");
224 switch($error)
225 {
226 case "-3":
227 return("-3 socket could not be created");
228 case "-4":
229 return("-4 dns lookup on hostname \"".$domain."\" failed");
230 case "-5":
231 return("-5 connection refused or timed out");
232 case "-6":
233 return("-6 fdopen() call failed");
234 case "-7":
235 return("-7 setvbuf() call failed");
236 }
237 return("could not connect to the host \"".$domain."\": ".$error);
238 }
239
240 Function SASLAuthenticate($mechanisms, $credentials, &$authenticated, &$mechanism)
241 {
242 $authenticated=0;
243 if(!function_exists("class_exists")
244 || !class_exists("sasl_client_class"))
245 {
246 $this->error="it is not possible to authenticate using the specified mechanism because the SASL library class is not loaded";
247 return(0);
248 }
249 $sasl=new sasl_client_class;
250 $sasl->SetCredential("user",$credentials["user"]);
251 $sasl->SetCredential("password",$credentials["password"]);
252 if(IsSet($credentials["realm"]))
253 $sasl->SetCredential("realm",$credentials["realm"]);
254 if(IsSet($credentials["workstation"]))
255 $sasl->SetCredential("workstation",$credentials["workstation"]);
256 if(IsSet($credentials["mode"]))
257 $sasl->SetCredential("mode",$credentials["mode"]);
258 do
259 {
260 $status=$sasl->Start($mechanisms,$message,$interactions);
261 }
262 while($status==SASL_INTERACT);
263 switch($status)
264 {
265 case SASL_CONTINUE:
266 break;
267 case SASL_NOMECH:
268 if(strlen($this->authentication_mechanism))
269 {
270 $this->error="authenticated mechanism ".$this->authentication_mechanism." may not be used: ".$sasl->error;
271 return(0);
272 }
273 break;
274 default:
275 $this->error="Could not start the SASL authentication client: ".$sasl->error;
276 return(0);
277 }
278 if(strlen($mechanism=$sasl->mechanism))
279 {
280 if($this->PutLine("AUTH ".$sasl->mechanism.(IsSet($message) ? " ".base64_encode($message) : ""))==0)
281 {
282 $this->error="Could not send the AUTH command";
283 return(0);
284 }
285 if(!$this->VerifyResultLines(array("235","334"),$responses))
286 return(0);
287 switch($this->result_code)
288 {
289 case "235":
290 $response="";
291 $authenticated=1;
292 break;
293 case "334":
294 $response=base64_decode($responses[0]);
295 break;
296 default:
297 $this->error="Authentication error: ".$responses[0];
298 return(0);
299 }
300 for(;!$authenticated;)
301 {
302 do
303 {
304 $status=$sasl->Step($response,$message,$interactions);
305 }
306 while($status==SASL_INTERACT);
307 switch($status)
308 {
309 case SASL_CONTINUE:
310 if($this->PutLine(base64_encode($message))==0)
311 {
312 $this->error="Could not send the authentication step message";
313 return(0);
314 }
315 if(!$this->VerifyResultLines(array("235","334"),$responses))
316 return(0);
317 switch($this->result_code)
318 {
319 case "235":
320 $response="";
321 $authenticated=1;
322 break;
323 case "334":
324 $response=base64_decode($responses[0]);
325 break;
326 default:
327 $this->error="Authentication error: ".$responses[0];
328 return(0);
329 }
330 break;
331 default:
332 $this->error="Could not process the SASL authentication step: ".$sasl->error;
333 return(0);
334 }
335 }
336 }
337 return(1);
338 }
339
340 /* Public methods */
341
342 Function Connect($domain="")
343 {
344 if(strcmp($this->state,"Disconnected"))
345 {
346 $this->error="connection is already established";
347 return(0);
348 }
349 $this->error=$error="";
350 $this->esmtp_host="";
351 $this->esmtp_extensions=array();
352 $hosts=array();
353 if($this->direct_delivery)
354 {
355 if(strlen($domain)==0)
356 return(1);
357 $hosts=$weights=$mxhosts=array();
358 $getmxrr=$this->getmxrr;
359 if(function_exists($getmxrr)
360 && $getmxrr($domain,$hosts,$weights))
361 {
362 for($host=0;$host<count($hosts);$host++)
363 $mxhosts[$weights[$host]]=$hosts[$host];
364 KSort($mxhosts);
365 for(Reset($mxhosts),$host=0;$host<count($mxhosts);Next($mxhosts),$host++)
366 $hosts[$host]=$mxhosts[Key($mxhosts)];
367 }
368 else
369 {
370 if(strcmp(@gethostbyname($domain),$domain)!=0)
371 $hosts[]=$domain;
372 }
373 }
374 else
375 {
376 if(strlen($this->host_name))
377 $hosts[]=$this->host_name;
378 if(strlen($this->pop3_auth_host))
379 {
380 $user=$this->user;
381 if(strlen($user)==0)
382 {
383 $this->error="it was not specified the POP3 authentication user";
384 return(0);
385 }
386 $password=$this->password;
387 if(strlen($password)==0)
388 {
389 $this->error="it was not specified the POP3 authentication password";
390 return(0);
391 }
392 $domain=$this->pop3_auth_host;
393 $this->error=$this->ConnectToHost($domain, $this->pop3_auth_port, "Resolving POP3 authentication host \"".$domain."\"...");
394 if(strlen($this->error))
395 return(0);
396 if(strlen($response=$this->GetLine())==0)
397 return(0);
398 if(strcmp($this->Tokenize($response," "),"+OK"))
399 {
400 $this->error="POP3 authentication server greeting was not found";
401 return(0);
402 }
403 if(!$this->PutLine("USER ".$this->user)
404 || strlen($response=$this->GetLine())==0)
405 return(0);
406 if(strcmp($this->Tokenize($response," "),"+OK"))
407 {
408 $this->error="POP3 authentication user was not accepted: ".$this->Tokenize("\r\n");
409 return(0);
410 }
411 if(!$this->PutLine("PASS ".$password)
412 || strlen($response=$this->GetLine())==0)
413 return(0);
414 if(strcmp($this->Tokenize($response," "),"+OK"))
415 {
416 $this->error="POP3 authentication password was not accepted: ".$this->Tokenize("\r\n");
417 return(0);
418 }
419 fclose($this->connection);
420 $this->connection=0;
421 }
422 }
423 if(count($hosts)==0)
424 {
425 $this->error="could not determine the SMTP to connect";
426 return(0);
427 }
428 for($host=0, $error="not connected";strlen($error) && $host<count($hosts);$host++)
429 {
430 $domain=$hosts[$host];
431 $error=$this->ConnectToHost($domain, $this->host_port, "Resolving SMTP server domain \"$domain\"...");
432 }
433 if(strlen($error))
434 {
435 $this->error=$error;
436 return(0);
437 }
438 $timeout=($this->data_timeout ? $this->data_timeout : $this->timeout);
439 if($timeout
440 && function_exists("socket_set_timeout"))
441 socket_set_timeout($this->connection,$timeout,0);
442 if($this->debug)
443 $this->OutputDebug("Connected to SMTP server \"".$domain."\".");
444 if(!strcmp($localhost=$this->localhost,"")
445 && !strcmp($localhost=getenv("SERVER_NAME"),"")
446 && !strcmp($localhost=getenv("HOST"),""))
447 $localhost="localhost";
448 $success=0;
449 if($this->VerifyResultLines("220",$responses)>0)
450 {
451 $fallback=1;
452 if($this->esmtp
453 || strlen($this->user))
454 {
455 if($this->PutLine("EHLO $localhost"))
456 {
457 if(($success_code=$this->VerifyResultLines("250",$responses))>0)
458 {
459 $this->esmtp_host=$this->Tokenize($responses[0]," ");
460 for($response=1;$response<count($responses);$response++)
461 {
462 $extension=strtoupper($this->Tokenize($responses[$response]," "));
463 $this->esmtp_extensions[$extension]=$this->Tokenize("");
464 }
465 $success=1;
466 $fallback=0;
467 }
468 else
469 {
470 if($success_code==0)
471 {
472 $code=$this->Tokenize($this->error," -");
473 switch($code)
474 {
475 case "421":
476 $fallback=0;
477 break;
478 }
479 }
480 }
481 }
482 else
483 $fallback=0;
484 }
485 if($fallback)
486 {
487 if($this->PutLine("HELO $localhost")
488 && $this->VerifyResultLines("250",$responses)>0)
489 $success=1;
490 }
491 if($success
492 && strlen($this->user)
493 && strlen($this->pop3_auth_host)==0)
494 {
495 if(!IsSet($this->esmtp_extensions["AUTH"]))
496 {
497 $this->error="server does not require authentication";
498 $success=0;
499 }
500 else
501 {
502 if(strlen($this->authentication_mechanism))
503 $mechanisms=array($this->authentication_mechanism);
504 else
505 {
506 $mechanisms=array();
507 for($authentication=$this->Tokenize($this->esmtp_extensions["AUTH"]," ");strlen($authentication);$authentication=$this->Tokenize(" "))
508 $mechanisms[]=$authentication;
509 }
510 $credentials=array(
511 "user"=>$this->user,
512 "password"=>$this->password
513 );
514 if(strlen($this->realm))
515 $credentials["realm"]=$this->realm;
516 if(strlen($this->workstation))
517 $credentials["workstation"]=$this->workstation;
518 $success=$this->SASLAuthenticate($mechanisms,$credentials,$authenticated,$mechanism);
519 if(!$success
520 && !strcmp($mechanism,"PLAIN"))
521 {
522 /*
523 * Author: Russell Robinson, 25 May 2003, http://www.tectite.com/
524 * Purpose: Try various AUTH PLAIN authentication methods.
525 */
526 $mechanisms=array("PLAIN");
527 $credentials=array(
528 "user"=>$this->user,
529 "password"=>$this->password
530 );
531 if(strlen($this->realm))
532 {
533 /*
534 * According to: http://www.sendmail.org/~ca/email/authrealms.html#authpwcheck_method
535 * some sendmails won't accept the realm, so try again without it
536 */
537 $success=$this->SASLAuthenticate($mechanisms,$credentials,$authenticated,$mechanism);
538 }
539 if(!$success)
540 {
541 /*
542 * It was seen an EXIM configuration like this:
543 * user^password^unused
544 */
545 $credentials["mode"]=SASL_PLAIN_EXIM_DOCUMENTATION_MODE;
546 $success=$this->SASLAuthenticate($mechanisms,$credentials,$authenticated,$mechanism);
547 }
548 if(!$success)
549 {
550 /*
551 * ... though: http://exim.work.de/exim-html-3.20/doc/html/spec_36.html
552 * specifies: ^user^password
553 */
554 $credentials["mode"]=SASL_PLAIN_EXIM_MODE;
555 $success=$this->SASLAuthenticate($mechanisms,$credentials,$authenticated,$mechanism);
556 }
557 }
558 if($success
559 && strlen($mechanism)==0)
560 {
561 $this->error="it is not supported any of the authentication mechanisms required by the server";
562 $success=0;
563 }
564 }
565 }
566 }
567 if($success)
568 {
569 $this->state="Connected";
570 $this->connected_domain=$domain;
571 }
572 else
573 {
574 fclose($this->connection);
575 $this->connection=0;
576 }
577 return($success);
578 }
579
580 Function MailFrom($sender)
581 {
582 if($this->direct_delivery)
583 {
584 switch($this->state)
585 {
586 case "Disconnected":
587 $this->direct_sender=$sender;
588 return(1);
589 case "Connected":
590 $sender=$this->direct_sender;
591 break;
592 default:
593 $this->error="direct delivery connection is already established and sender is already set";
594 return(0);
595 }
596 }
597 else
598 {
599 if(strcmp($this->state,"Connected"))
600 {
601 $this->error="connection is not in the initial state";
602 return(0);
603 }
604 }
605 $this->error="";
606 if(!$this->PutLine("MAIL FROM:<$sender>"))
607 return(0);
608 if(!IsSet($this->esmtp_extensions["PIPELINING"])
609 && $this->VerifyResultLines("250",$responses)<=0)
610 return(0);
611 $this->state="SenderSet";
612 if(IsSet($this->esmtp_extensions["PIPELINING"]))
613 $this->pending_sender=1;
614 $this->pending_recipients=0;
615 return(1);
616 }
617
618 Function SetRecipient($recipient)
619 {
620 if($this->direct_delivery)
621 {
622 if(GetType($at=strrpos($recipient,"@"))!="integer")
623 return("it was not specified a valid direct recipient");
624 $domain=substr($recipient,$at+1);
625 switch($this->state)
626 {
627 case "Disconnected":
628 if(!$this->Connect($domain))
629 return(0);
630 if(!$this->MailFrom(""))
631 {
632 $error=$this->error;
633 $this->Disconnect();
634 $this->error=$error;
635 return(0);
636 }
637 break;
638 case "SenderSet":
639 case "RecipientSet":
640 if(strcmp($this->connected_domain,$domain))
641 {
642 $this->error="it is not possible to deliver directly to recipients of different domains";
643 return(0);
644 }
645 break;
646 default:
647 $this->error="connection is already established and the recipient is already set";
648 return(0);
649 }
650 }
651 else
652 {
653 switch($this->state)
654 {
655 case "SenderSet":
656 case "RecipientSet":
657 break;
658 default:
659 $this->error="connection is not in the recipient setting state";
660 return(0);
661 }
662 }
663 $this->error="";
664 if(!$this->PutLine("RCPT TO:<$recipient>"))
665 return(0);
666 if(IsSet($this->esmtp_extensions["PIPELINING"]))
667 {
668 $this->pending_recipients++;
669 if($this->pending_recipients>=$this->maximum_piped_recipients)
670 {
671 if(!$this->FlushRecipients())
672 return(0);
673 }
674 }
675 else
676 {
677 if($this->VerifyResultLines(array("250","251"),$responses)<=0)
678 return(0);
679 }
680 $this->state="RecipientSet";
681 return(1);
682 }
683
684 Function StartData()
685 {
686 if(strcmp($this->state,"RecipientSet"))
687 {
688 $this->error="connection is not in the start sending data state";
689 return(0);
690 }
691 $this->error="";
692 if(!$this->PutLine("DATA"))
693 return(0);
694 if($this->pending_recipients)
695 {
696 if(!$this->FlushRecipients())
697 return(0);
698 }
699 if($this->VerifyResultLines("354",$responses)<=0)
700 return(0);
701 $this->state="SendingData";
702 return(1);
703 }
704
705 Function PrepareData(&$data,&$output,$preg=1)
706 {
707 if($preg
708 && function_exists("preg_replace"))
709 $output=preg_replace(array("/\n\n|\r\r/","/(^|[^\r])\n/","/\r([^\n]|\$)/D","/(^|\n)\\./"),array("\r\n\r\n","\\1\r\n","\r\n\\1","\\1.."),$data);
710 else
711 $output=ereg_replace("(^|\n)\\.","\\1..",ereg_replace("\r([^\n]|\$)","\r\n\\1",ereg_replace("(^|[^\r])\n","\\1\r\n",ereg_replace("\n\n|\r\r","\r\n\r\n",$data))));
712 }
713
714 Function SendData($data)
715 {
716 if(strcmp($this->state,"SendingData"))
717 {
718 $this->error="connection is not in the sending data state";
719 return(0);
720 }
721 $this->error="";
722 return($this->PutData($data));
723 }
724
725 Function EndSendingData()
726 {
727 if(strcmp($this->state,"SendingData"))
728 {
729 $this->error="connection is not in the sending data state";
730 return(0);
731 }
732 $this->error="";
733 if(!$this->PutLine("\r\n.")
734 || $this->VerifyResultLines("250",$responses)<=0)
735 return(0);
736 $this->state="Connected";
737 return(1);
738 }
739
740 Function ResetConnection()
741 {
742 switch($this->state)
743 {
744 case "Connected":
745 return(1);
746 case "SendingData":
747 $this->error="can not reset the connection while sending data";
748 return(0);
749 case "Disconnected":
750 $this->error="can not reset the connection before it is established";
751 return(0);
752 }
753 $this->error="";
754 if(!$this->PutLine("RSET")
755 || $this->VerifyResultLines("250",$responses)<=0)
756 return(0);
757 $this->state="Connected";
758 return(1);
759 }
760
761 Function Disconnect($quit=1)
762 {
763 if(!strcmp($this->state,"Disconnected"))
764 {
765 $this->error="it was not previously established a SMTP connection";
766 return(0);
767 }
768 $this->error="";
769 if(!strcmp($this->state,"Connected")
770 && $quit
771 && (!$this->PutLine("QUIT")
772 || $this->VerifyResultLines("221",$responses)<=0))
773 return(0);
774 fclose($this->connection);
775 $this->connection=0;
776 $this->state="Disconnected";
777 if($this->debug)
778 $this->OutputDebug("Disconnected.");
779 return(1);
780 }
781
782 Function SendMessage($sender,$recipients,$headers,$body)
783 {
784 if(($success=$this->Connect()))
785 {
786 if(($success=$this->MailFrom($sender)))
787 {
788 for($recipient=0;$recipient<count($recipients);$recipient++)
789 {
790 if(!($success=$this->SetRecipient($recipients[$recipient])))
791 break;
792 }
793 if($success
794 && ($success=$this->StartData()))
795 {
796 for($header_data="",$header=0;$header<count($headers);$header++)
797 $header_data.=$headers[$header]."\r\n";
798 if(($success=$this->SendData($header_data."\r\n")))
799 {
800 $this->PrepareData($body,$body_data);
801 $success=$this->SendData($body_data);
802 }
803 if($success)
804 $success=$this->EndSendingData();
805 }
806 }
807 $error=$this->error;
808 $disconnect_success=$this->Disconnect($success);
809 if($success)
810 $success=$disconnect_success;
811 else
812 $this->error=$error;
813 }
814 return($success);
815 }
816
817 };
818
819 ?>
2 /*
3 * smtp.php
4 *
5 * @(#) $Header: /home/mlemos/cvsroot/smtp/smtp.php,v 1.37 2004/10/05 04:00:46 mlemos Exp $
6 *
7 */
8
9 class smtp_class
10 {
11 var $user="";
12 var $realm="";
13 var $password="";
14 var $workstation="";
15 var $authentication_mechanism="";
16 var $host_name="";
17 var $host_port=25;
18 var $localhost="";
19 var $timeout=0;
20 var $data_timeout=0;
21 var $direct_delivery=0;
22 var $error="";
23 var $debug=0;
24 var $html_debug=0;
25 var $esmtp=1;
26 var $esmtp_host="";
27 var $esmtp_extensions=array();
28 var $maximum_piped_recipients=100;
29 var $exclude_address="";
30 var $getmxrr="GetMXRR";
31 var $pop3_auth_host="";
32 var $pop3_auth_port=110;
33
34 /* private variables - DO NOT ACCESS */
35
36 var $state="Disconnected";
37 var $connection=0;
38 var $pending_recipients=0;
39 var $next_token="";
40 var $direct_sender="";
41 var $connected_domain="";
42 var $result_code;
43
44 /* Private methods - DO NOT CALL */
45
46 Function Tokenize($string,$separator="")
47 {
48 if(!strcmp($separator,""))
49 {
50 $separator=$string;
51 $string=$this->next_token;
52 }
53 for($character=0;$character<strlen($separator);$character++)
54 {
55 if(GetType($position=strpos($string,$separator[$character]))=="integer")
56 $found=(IsSet($found) ? min($found,$position) : $position);
57 }
58 if(IsSet($found))
59 {
60 $this->next_token=substr($string,$found+1);
61 return(substr($string,0,$found));
62 }
63 else
64 {
65 $this->next_token="";
66 return($string);
67 }
68 }
69
70 Function OutputDebug($message)
71 {
72 $message.="\n";
73 if($this->html_debug)
74 $message=str_replace("\n","<br />\n",HtmlEntities($message));
75 echo $message;
76 flush();
77 }
78
79 Function SetDataAccessError($error)
80 {
81 $this->error=$error;
82 if(function_exists("socket_get_status"))
83 {
84 $status=socket_get_status($this->connection);
85 if($status["timed_out"])
86 $this->error.=": data access time out";
87 elseif($status["eof"])
88 $this->error.=": the server disconnected";
89 }
90 }
91
92 Function GetLine()
93 {
94 for($line="";;)
95 {
96 if(feof($this->connection))
97 {
98 $this->error="reached the end of data while reading from the SMTP server conection";
99 return("");
100 }
101 if(GetType($data=fgets($this->connection,100))!="string"
102 || strlen($data)==0)
103 {
104 $this->SetDataAccessError("it was not possible to read line from the SMTP server");
105 return("");
106 }
107 $line.=$data;
108 $length=strlen($line);
109 if($length>=2
110 && substr($line,$length-2,2)=="\r\n")
111 {
112 $line=substr($line,0,$length-2);
113 if($this->debug)
114 $this->OutputDebug("S $line");
115 return($line);
116 }
117 }
118 }
119
120 Function PutLine($line)
121 {
122 if($this->debug)
123 $this->OutputDebug("C $line");
124 if(!fputs($this->connection,"$line\r\n"))
125 {
126 $this->SetDataAccessError("it was not possible to send a line to the SMTP server");
127 return(0);
128 }
129 return(1);
130 }
131
132 Function PutData(&$data)
133 {
134 if(strlen($data))
135 {
136 if($this->debug)
137 $this->OutputDebug("C $data");
138 if(!fputs($this->connection,$data))
139 {
140 $this->SetDataAccessError("it was not possible to send data to the SMTP server");
141 return(0);
142 }
143 }
144 return(1);
145 }
146
147 Function VerifyResultLines($code,&$responses)
148 {
149 $responses=array();
150 Unset($this->result_code);
151 while(strlen($line=$this->GetLine($this->connection)))
152 {
153 if(IsSet($this->result_code))
154 {
155 if(strcmp($this->Tokenize($line," -"),$this->result_code))
156 {
157 $this->error=$line;
158 return(0);
159 }
160 }
161 else
162 {
163 $this->result_code=$this->Tokenize($line," -");
164 if(GetType($code)=="array")
165 {
166 for($codes=0;$codes<count($code) && strcmp($this->result_code,$code[$codes]);$codes++);
167 if($codes>=count($code))
168 {
169 $this->error=$line;
170 return(0);
171 }
172 }
173 else
174 {
175 if(strcmp($this->result_code,$code))
176 {
177 $this->error=$line;
178 return(0);
179 }
180 }
181 }
182 $responses[]=$this->Tokenize("");
183 if(!strcmp($this->result_code,$this->Tokenize($line," ")))
184 return(1);
185 }
186 return(-1);
187 }
188
189 Function FlushRecipients()
190 {
191 if($this->pending_sender)
192 {
193 if($this->VerifyResultLines("250",$responses)<=0)
194 return(0);
195 $this->pending_sender=0;
196 }
197 for(;$this->pending_recipients;$this->pending_recipients--)
198 {
199 if($this->VerifyResultLines(array("250","251"),$responses)<=0)
200 return(0);
201 }
202 return(1);
203 }
204
205 Function ConnectToHost($domain, $port, $resolve_message)
206 {
207 if(ereg('^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$',$domain))
208 $ip=$domain;
209 else
210 {
211 if($this->debug)
212 $this->OutputDebug($resolve_message);
213 if(!strcmp($ip=@gethostbyname($domain),$domain))
214 return("could not resolve host \"".$domain."\"");
215 }
216 if(strlen($this->exclude_address)
217 && !strcmp(@gethostbyname($this->exclude_address),$ip))
218 return("domain \"".$domain."\" resolved to an address excluded to be valid");
219 if($this->debug)
220 $this->OutputDebug("Connecting to host address \"".$ip."\"...");
221 if(($this->connection=($this->timeout ? @fsockopen($ip,$port,$errno,$error,$this->timeout) : @fsockopen($ip,$port))))
222 return("");
223 $error=($this->timeout ? strval($error) : "??");
224 switch($error)
225 {
226 case "-3":
227 return("-3 socket could not be created");
228 case "-4":
229 return("-4 dns lookup on hostname \"".$domain."\" failed");
230 case "-5":
231 return("-5 connection refused or timed out");
232 case "-6":
233 return("-6 fdopen() call failed");
234 case "-7":
235 return("-7 setvbuf() call failed");
236 }
237 return("could not connect to the host \"".$domain."\": ".$error);
238 }
239
240 Function SASLAuthenticate($mechanisms, $credentials, &$authenticated, &$mechanism)
241 {
242 $authenticated=0;
243 if(!function_exists("class_exists")
244 || !class_exists("sasl_client_class"))
245 {
246 $this->error="it is not possible to authenticate using the specified mechanism because the SASL library class is not loaded";
247 return(0);
248 }
249 $sasl=new sasl_client_class;
250 $sasl->SetCredential("user",$credentials["user"]);
251 $sasl->SetCredential("password",$credentials["password"]);
252 if(IsSet($credentials["realm"]))
253 $sasl->SetCredential("realm",$credentials["realm"]);
254 if(IsSet($credentials["workstation"]))
255 $sasl->SetCredential("workstation",$credentials["workstation"]);
256 if(IsSet($credentials["mode"]))
257 $sasl->SetCredential("mode",$credentials["mode"]);
258 do
259 {
260 $status=$sasl->Start($mechanisms,$message,$interactions);
261 }
262 while($status==SASL_INTERACT);
263 switch($status)
264 {
265 case SASL_CONTINUE:
266 break;
267 case SASL_NOMECH:
268 if(strlen($this->authentication_mechanism))
269 {
270 $this->error="authenticated mechanism ".$this->authentication_mechanism." may not be used: ".$sasl->error;
271 return(0);
272 }
273 break;
274 default:
275 $this->error="Could not start the SASL authentication client: ".$sasl->error;
276 return(0);
277 }
278 if(strlen($mechanism=$sasl->mechanism))
279 {
280 if($this->PutLine("AUTH ".$sasl->mechanism.(IsSet($message) ? " ".base64_encode($message) : ""))==0)
281 {
282 $this->error="Could not send the AUTH command";
283 return(0);
284 }
285 if(!$this->VerifyResultLines(array("235","334"),$responses))
286 return(0);
287 switch($this->result_code)
288 {
289 case "235":
290 $response="";
291 $authenticated=1;
292 break;
293 case "334":
294 $response=base64_decode($responses[0]);
295 break;
296 default:
297 $this->error="Authentication error: ".$responses[0];
298 return(0);
299 }
300 for(;!$authenticated;)
301 {
302 do
303 {
304 $status=$sasl->Step($response,$message,$interactions);
305 }
306 while($status==SASL_INTERACT);
307 switch($status)
308 {
309 case SASL_CONTINUE:
310 if($this->PutLine(base64_encode($message))==0)
311 {
312 $this->error="Could not send the authentication step message";
313 return(0);
314 }
315 if(!$this->VerifyResultLines(array("235","334"),$responses))
316 return(0);
317 switch($this->result_code)
318 {
319 case "235":
320 $response="";
321 $authenticated=1;
322 break;
323 case "334":
324 $response=base64_decode($responses[0]);
325 break;
326 default:
327 $this->error="Authentication error: ".$responses[0];
328 return(0);
329 }
330 break;
331 default:
332 $this->error="Could not process the SASL authentication step: ".$sasl->error;
333 return(0);
334 }
335 }
336 }
337 return(1);
338 }
339
340 /* Public methods */
341
342 Function Connect($domain="")
343 {
344 if(strcmp($this->state,"Disconnected"))
345 {
346 $this->error="connection is already established";
347 return(0);
348 }
349 $this->error=$error="";
350 $this->esmtp_host="";
351 $this->esmtp_extensions=array();
352 $hosts=array();
353 if($this->direct_delivery)
354 {
355 if(strlen($domain)==0)
356 return(1);
357 $hosts=$weights=$mxhosts=array();
358 $getmxrr=$this->getmxrr;
359 if(function_exists($getmxrr)
360 && $getmxrr($domain,$hosts,$weights))
361 {
362 for($host=0;$host<count($hosts);$host++)
363 $mxhosts[$weights[$host]]=$hosts[$host];
364 KSort($mxhosts);
365 for(Reset($mxhosts),$host=0;$host<count($mxhosts);Next($mxhosts),$host++)
366 $hosts[$host]=$mxhosts[Key($mxhosts)];
367 }
368 else
369 {
370 if(strcmp(@gethostbyname($domain),$domain)!=0)
371 $hosts[]=$domain;
372 }
373 }
374 else
375 {
376 if(strlen($this->host_name))
377 $hosts[]=$this->host_name;
378 if(strlen($this->pop3_auth_host))
379 {
380 $user=$this->user;
381 if(strlen($user)==0)
382 {
383 $this->error="it was not specified the POP3 authentication user";
384 return(0);
385 }
386 $password=$this->password;
387 if(strlen($password)==0)
388 {
389 $this->error="it was not specified the POP3 authentication password";
390 return(0);
391 }
392 $domain=$this->pop3_auth_host;
393 $this->error=$this->ConnectToHost($domain, $this->pop3_auth_port, "Resolving POP3 authentication host \"".$domain."\"...");
394 if(strlen($this->error))
395 return(0);
396 if(strlen($response=$this->GetLine())==0)
397 return(0);
398 if(strcmp($this->Tokenize($response," "),"+OK"))
399 {
400 $this->error="POP3 authentication server greeting was not found";
401 return(0);
402 }
403 if(!$this->PutLine("USER ".$this->user)
404 || strlen($response=$this->GetLine())==0)
405 return(0);
406 if(strcmp($this->Tokenize($response," "),"+OK"))
407 {
408 $this->error="POP3 authentication user was not accepted: ".$this->Tokenize("\r\n");
409 return(0);
410 }
411 if(!$this->PutLine("PASS ".$password)
412 || strlen($response=$this->GetLine())==0)
413 return(0);
414 if(strcmp($this->Tokenize($response," "),"+OK"))
415 {
416 $this->error="POP3 authentication password was not accepted: ".$this->Tokenize("\r\n");
417 return(0);
418 }
419 fclose($this->connection);
420 $this->connection=0;
421 }
422 }
423 if(count($hosts)==0)
424 {
425 $this->error="could not determine the SMTP to connect";
426 return(0);
427 }
428 for($host=0, $error="not connected";strlen($error) && $host<count($hosts);$host++)
429 {
430 $domain=$hosts[$host];
431 $error=$this->ConnectToHost($domain, $this->host_port, "Resolving SMTP server domain \"$domain\"...");
432 }
433 if(strlen($error))
434 {
435 $this->error=$error;
436 return(0);
437 }
438 $timeout=($this->data_timeout ? $this->data_timeout : $this->timeout);
439 if($timeout
440 && function_exists("socket_set_timeout"))
441 socket_set_timeout($this->connection,$timeout,0);
442 if($this->debug)
443 $this->OutputDebug("Connected to SMTP server \"".$domain."\".");
444 if(!strcmp($localhost=$this->localhost,"")
445 && !strcmp($localhost=getenv("SERVER_NAME"),"")
446 && !strcmp($localhost=getenv("HOST"),""))
447 $localhost="localhost";
448 $success=0;
449 if($this->VerifyResultLines("220",$responses)>0)
450 {
451 $fallback=1;
452 if($this->esmtp
453 || strlen($this->user))
454 {
455 if($this->PutLine("EHLO $localhost"))
456 {
457 if(($success_code=$this->VerifyResultLines("250",$responses))>0)
458 {
459 $this->esmtp_host=$this->Tokenize($responses[0]," ");
460 for($response=1;$response<count($responses);$response++)
461 {
462 $extension=strtoupper($this->Tokenize($responses[$response]," "));
463 $this->esmtp_extensions[$extension]=$this->Tokenize("");
464 }
465 $success=1;
466 $fallback=0;
467 }
468 else
469 {
470 if($success_code==0)
471 {
472 $code=$this->Tokenize($this->error," -");
473 switch($code)
474 {
475 case "421":
476 $fallback=0;
477 break;
478 }
479 }
480 }
481 }
482 else
483 $fallback=0;
484 }
485 if($fallback)
486 {
487 if($this->PutLine("HELO $localhost")
488 && $this->VerifyResultLines("250",$responses)>0)
489 $success=1;
490 }
491 if($success
492 && strlen($this->user)
493 && strlen($this->pop3_auth_host)==0)
494 {
495 if(!IsSet($this->esmtp_extensions["AUTH"]))
496 {
497 $this->error="server does not require authentication";
498 $success=0;
499 }
500 else
501 {
502 if(strlen($this->authentication_mechanism))
503 $mechanisms=array($this->authentication_mechanism);
504 else
505 {
506 $mechanisms=array();
507 for($authentication=$this->Tokenize($this->esmtp_extensions["AUTH"]," ");strlen($authentication);$authentication=$this->Tokenize(" "))
508 $mechanisms[]=$authentication;
509 }
510 $credentials=array(
511 "user"=>$this->user,
512 "password"=>$this->password
513 );
514 if(strlen($this->realm))
515 $credentials["realm"]=$this->realm;
516 if(strlen($this->workstation))
517 $credentials["workstation"]=$this->workstation;
518 $success=$this->SASLAuthenticate($mechanisms,$credentials,$authenticated,$mechanism);
519 if(!$success
520 && !strcmp($mechanism,"PLAIN"))
521 {
522 /*
523 * Author: Russell Robinson, 25 May 2003, http://www.tectite.com/
524 * Purpose: Try various AUTH PLAIN authentication methods.
525 */
526 $mechanisms=array("PLAIN");
527 $credentials=array(
528 "user"=>$this->user,
529 "password"=>$this->password
530 );
531 if(strlen($this->realm))
532 {
533 /*
534 * According to: http://www.sendmail.org/~ca/email/authrealms.html#authpwcheck_method
535 * some sendmails won't accept the realm, so try again without it
536 */
537 $success=$this->SASLAuthenticate($mechanisms,$credentials,$authenticated,$mechanism);
538 }
539 if(!$success)
540 {
541 /*
542 * It was seen an EXIM configuration like this:
543 * user^password^unused
544 */
545 $credentials["mode"]=SASL_PLAIN_EXIM_DOCUMENTATION_MODE;
546 $success=$this->SASLAuthenticate($mechanisms,$credentials,$authenticated,$mechanism);
547 }
548 if(!$success)
549 {
550 /*
551 * ... though: http://exim.work.de/exim-html-3.20/doc/html/spec_36.html
552 * specifies: ^user^password
553 */
554 $credentials["mode"]=SASL_PLAIN_EXIM_MODE;
555 $success=$this->SASLAuthenticate($mechanisms,$credentials,$authenticated,$mechanism);
556 }
557 }
558 if($success
559 && strlen($mechanism)==0)
560 {
561 $this->error="it is not supported any of the authentication mechanisms required by the server";
562 $success=0;
563 }
564 }
565 }
566 }
567 if($success)
568 {
569 $this->state="Connected";
570 $this->connected_domain=$domain;
571 }
572 else
573 {
574 fclose($this->connection);
575 $this->connection=0;
576 }
577 return($success);
578 }
579
580 Function MailFrom($sender)
581 {
582 if($this->direct_delivery)
583 {
584 switch($this->state)
585 {
586 case "Disconnected":
587 $this->direct_sender=$sender;
588 return(1);
589 case "Connected":
590 $sender=$this->direct_sender;
591 break;
592 default:
593 $this->error="direct delivery connection is already established and sender is already set";
594 return(0);
595 }
596 }
597 else
598 {
599 if(strcmp($this->state,"Connected"))
600 {
601 $this->error="connection is not in the initial state";
602 return(0);
603 }
604 }
605 $this->error="";
606 if(!$this->PutLine("MAIL FROM:<$sender>"))
607 return(0);
608 if(!IsSet($this->esmtp_extensions["PIPELINING"])
609 && $this->VerifyResultLines("250",$responses)<=0)
610 return(0);
611 $this->state="SenderSet";
612 if(IsSet($this->esmtp_extensions["PIPELINING"]))
613 $this->pending_sender=1;
614 $this->pending_recipients=0;
615 return(1);
616 }
617
618 Function SetRecipient($recipient)
619 {
620 if($this->direct_delivery)
621 {
622 if(GetType($at=strrpos($recipient,"@"))!="integer")
623 return("it was not specified a valid direct recipient");
624 $domain=substr($recipient,$at+1);
625 switch($this->state)
626 {
627 case "Disconnected":
628 if(!$this->Connect($domain))
629 return(0);
630 if(!$this->MailFrom(""))
631 {
632 $error=$this->error;
633 $this->Disconnect();
634 $this->error=$error;
635 return(0);
636 }
637 break;
638 case "SenderSet":
639 case "RecipientSet":
640 if(strcmp($this->connected_domain,$domain))
641 {
642 $this->error="it is not possible to deliver directly to recipients of different domains";
643 return(0);
644 }
645 break;
646 default:
647 $this->error="connection is already established and the recipient is already set";
648 return(0);
649 }
650 }
651 else
652 {
653 switch($this->state)
654 {
655 case "SenderSet":
656 case "RecipientSet":
657 break;
658 default:
659 $this->error="connection is not in the recipient setting state";
660 return(0);
661 }
662 }
663 $this->error="";
664 if(!$this->PutLine("RCPT TO:<$recipient>"))
665 return(0);
666 if(IsSet($this->esmtp_extensions["PIPELINING"]))
667 {
668 $this->pending_recipients++;
669 if($this->pending_recipients>=$this->maximum_piped_recipients)
670 {
671 if(!$this->FlushRecipients())
672 return(0);
673 }
674 }
675 else
676 {
677 if($this->VerifyResultLines(array("250","251"),$responses)<=0)
678 return(0);
679 }
680 $this->state="RecipientSet";
681 return(1);
682 }
683
684 Function StartData()
685 {
686 if(strcmp($this->state,"RecipientSet"))
687 {
688 $this->error="connection is not in the start sending data state";
689 return(0);
690 }
691 $this->error="";
692 if(!$this->PutLine("DATA"))
693 return(0);
694 if($this->pending_recipients)
695 {
696 if(!$this->FlushRecipients())
697 return(0);
698 }
699 if($this->VerifyResultLines("354",$responses)<=0)
700 return(0);
701 $this->state="SendingData";
702 return(1);
703 }
704
705 Function PrepareData(&$data,&$output,$preg=1)
706 {
707 if($preg
708 && function_exists("preg_replace"))
709 $output=preg_replace(array("/\n\n|\r\r/","/(^|[^\r])\n/","/\r([^\n]|\$)/D","/(^|\n)\\./"),array("\r\n\r\n","\\1\r\n","\r\n\\1","\\1.."),$data);
710 else
711 $output=ereg_replace("(^|\n)\\.","\\1..",ereg_replace("\r([^\n]|\$)","\r\n\\1",ereg_replace("(^|[^\r])\n","\\1\r\n",ereg_replace("\n\n|\r\r","\r\n\r\n",$data))));
712 }
713
714 Function SendData($data)
715 {
716 if(strcmp($this->state,"SendingData"))
717 {
718 $this->error="connection is not in the sending data state";
719 return(0);
720 }
721 $this->error="";
722 return($this->PutData($data));
723 }
724
725 Function EndSendingData()
726 {
727 if(strcmp($this->state,"SendingData"))
728 {
729 $this->error="connection is not in the sending data state";
730 return(0);
731 }
732 $this->error="";
733 if(!$this->PutLine("\r\n.")
734 || $this->VerifyResultLines("250",$responses)<=0)
735 return(0);
736 $this->state="Connected";
737 return(1);
738 }
739
740 Function ResetConnection()
741 {
742 switch($this->state)
743 {
744 case "Connected":
745 return(1);
746 case "SendingData":
747 $this->error="can not reset the connection while sending data";
748 return(0);
749 case "Disconnected":
750 $this->error="can not reset the connection before it is established";
751 return(0);
752 }
753 $this->error="";
754 if(!$this->PutLine("RSET")
755 || $this->VerifyResultLines("250",$responses)<=0)
756 return(0);
757 $this->state="Connected";
758 return(1);
759 }
760
761 Function Disconnect($quit=1)
762 {
763 if(!strcmp($this->state,"Disconnected"))
764 {
765 $this->error="it was not previously established a SMTP connection";
766 return(0);
767 }
768 $this->error="";
769 if(!strcmp($this->state,"Connected")
770 && $quit
771 && (!$this->PutLine("QUIT")
772 || $this->VerifyResultLines("221",$responses)<=0))
773 return(0);
774 fclose($this->connection);
775 $this->connection=0;
776 $this->state="Disconnected";
777 if($this->debug)
778 $this->OutputDebug("Disconnected.");
779 return(1);
780 }
781
782 Function SendMessage($sender,$recipients,$headers,$body)
783 {
784 if(($success=$this->Connect()))
785 {
786 if(($success=$this->MailFrom($sender)))
787 {
788 for($recipient=0;$recipient<count($recipients);$recipient++)
789 {
790 if(!($success=$this->SetRecipient($recipients[$recipient])))
791 break;
792 }
793 if($success
794 && ($success=$this->StartData()))
795 {
796 for($header_data="",$header=0;$header<count($headers);$header++)
797 $header_data.=$headers[$header]."\r\n";
798 if(($success=$this->SendData($header_data."\r\n")))
799 {
800 $this->PrepareData($body,$body_data);
801 $success=$this->SendData($body_data);
802 }
803 if($success)
804 $success=$this->EndSendingData();
805 }
806 }
807 $error=$this->error;
808 $disconnect_success=$this->Disconnect($success);
809 if($success)
810 $success=$disconnect_success;
811 else
812 $this->error=$error;
813 }
814 return($success);
815 }
816
817 };
818
819 ?>